Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use subclasses of Net::HTTPRequest instead of Net::HTTPGenericRequest #647

Closed

Conversation

fotos
Copy link

@fotos fotos commented Dec 26, 2016

Fixes a problem with net-http-persistent adapter that is checking the subclasses of Net::HTTPRequest for idempotence support.

Instead of directly instantiating Net::HTTPGenericRequest, use the appropriate Net::HTTPRequest subclass.

We stumbled upon this situation at @catawiki when we switched our internal API client to faraday (backed by net-http-persistent). Whenever we reloaded our nginx instances we saw Net::HTTP::Persistent::Error: too many connection resets (due to end of file reached - EOFError) exception being thrown to the application level, due to the sockets being closed.

It took a lot of trial and error and quite some digging to figure out what's going on even tho we haven't changed the underlying http library (net-http-persistent). There are a couple of reports online that might to be related to this issue (sparklemotion/mechanize#123, drbrain/net-http-persistent#37) and a couple of gems such as elasticsearch-ruby might be affected by this issue depending on the configuration.

The problem is that faraday is creating a class of Net::HTTPGenericRequest for the net-http adapter. Yet the net-http-persistent gem (whose faraday adapter implementation is subclassing the net-http adapter) is expecting a Net::HTTPRequest subclass. Based on the class it decides whether a request is idempotent.

If a request is idempotent then it automatically retries the request once.

As a temporary workaround we used the Faraday::Request::Retry middleware as follows:

client.use ::Faraday::Request::Retry, :max => 1, :exceptions => [::Net::HTTP::Persistent::Error]

(max is set intentionally to 1)

The idea for the approach in this PR is liberally taken from mechanize.

Note: this change is breaking the specs. In Ruby 1.8, 1.9.3, 2.1 the Net::Http::Options is marked as not returning a body. This has been fixed in Ruby 2.2, even tho OPTIONS may have a request body since RFC2616. Previously the specs passed because the check was :head != env[:method]. So I'm not sure what's the appropriate approach for the specs or if this change is acceptable overall (the behaviour now depends on the Ruby version?).

I'll open I've opened PR #83 to net-http-persistent as well to use #method instead of checking the class hierarchy.

@mislav what do you think?

@fotos
Copy link
Author

fotos commented Jan 30, 2017

@iMacTia do you want me to provide a proof-of-concept setup that demonstrates the problem?

@iMacTia
Copy link
Member

iMacTia commented Jan 31, 2017

Hi @fotos.
First of all, thanks for your deep and extensive investigation on this issue.
At first glance I thought I was missing some background on it so tried to leave it to @mislav, but I spent some time (not too much though, busy period!) to have a look and I think I understand what's going on now.
The first thing to notice is that we are clearly breaking a Net::HTTP::Persistent requirement as you said:

+req+ must be a Net::HTTPRequest subclass (see Net::HTTP for a list).

This put us in a situation where we should address the issue on our side, as they're pretty clear on theirs.

Still, I'm a bit concerned about the issue with Ruby < 2.2 compatibility as we definitely want to keep supporting 1.9.3+.
I'm ok with your PR, but we definitely need to make those tests green.
RFC2616 is pretty clear as well, stating that OPTIONS requests CAN have a body.
So, here are the options for us:

  1. We remove the test that check the options body to be missing
  2. We change the test check based on the Ruby version.

To be honest I don't fully support neither of them. The former seems dangerous (we're removing a test that could prove itself useful), while the latter seems over-complicated.

If I really have to pick one, I would probably go for the latter to have the best test-coverage, accompanied with a descriptive comment on why we're doing that.

As a sidenote, what can you tell me about the other error?

Error:
Adapters::NetHttpPersistentTest#test_POST_sends_files:
Net::HTTP::Persistent::Error: too many bad responses after 7 requests on 12200180, last used 0.010148985 seconds ago

Instead of directly instantiating Net::HTTPGenericRequest, use the
appropriate Net::HTTPRequest subclass.

Fixes a problem with net-http-persistent adapter that is checking the
subclasses of Net::HTTPRequest for idempotence support.
@fotos fotos force-pushed the fix-net-http-persistent-initialization branch from eb27d01 to 90bdb60 Compare October 2, 2018 18:21
@iMacTia iMacTia closed this Feb 20, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants